:- ensure_loaded(behavioral_programming).
:- ensure_loaded(galaxy_formatter).
% :- ensure_loaded(galaxy_code_formatter).

idgen(bevent_id).
% bevent_id(E, Loc).
idgen(bthread_state_id).
% bthread_state_id(T, S, ID).
idgen(bthread_id).
% bthread_id(T, ID).
idgen(loop_index).

go_to_trigger_libs_dir:-
        exists_directory(triggerLibs),!,
        syso('triggerLibs dir exists!'),
        go_to_trigger_libs_dir_helper.
go_to_trigger_libs_dir:-
        make_directory(triggerLibs),!,
        syso('created triggerLibs dir!'),
        go_to_trigger_libs_dir_helper.
go_to_trigger_libs_dir_helper:-
        working_directory(CWD,CWD),
        % syso('cwd:'),
        % syso(CWD),
        absolute_file_name(triggerLibs,Absolute),
        working_directory(CWD,Absolute).

go_back_to_base:-
        working_directory(CWD,CWD),
        file_directory_name(CWD,Base),
        % syso('going back to base:'),
        % syso(Base),
        working_directory(CWD,Base).

generate(Spec):-
	read_bpspec(Spec),
        go_to_trigger_libs_dir,
	atom_concat(Spec, '.galaxy', OutputFilename),
	tell(OutputFilename),
	open(OutputFilename, write, _OutputStream, []),
	format('//generated using BPPM, written by Moshe Weinstock'),nl,
        % atom_concat(Spec,'_code',IncludeCodeFuncs),
        % include_file(IncludeCodeFuncs),
	gen_conventions,
	syso('generating spec <~w>\n',[Spec]),
	gen_spec_globals(Spec),
	% ID generation
	forall(bthread(Spec,T,_P),
	       (bthread_id_idgen(TID),
		assert(bthread_id(T,TID)))),
	forall(state(Spec, T, S),
	       (bthread_state_id_idgen(ID),
		assert(bthread_state_id(T, S, ID)))),
	% generate events
	atom_concat(Spec,' BEVENTS',BEventsComment),
	comment_sorround((forall(bevent(Spec, E),
				gen_bevent(Spec, E))),
			 BEventsComment),
        % generate bthread trigger vars
	atom_concat(Spec,' BThread code vars',CodeVarsComment),
        comment_sorround(forall(state(Spec,T,S),
                                (spec_bthread_state_member_name(Spec,T,S,
                                                                code,
                                                                CodeVarName),
                                 var_declarator(trigger,CodeVarName))),
                         CodeVarsComment),
        % generate bthread event request triggers
        atom_concat(Spec,' BThread event-request trigger vars',
                    EventRequestComment),
        comment_sorround(forall(state(Spec,T,S),
                                (spec_bthread_state_member_name(Spec,T,S,
                                                                event_request,
                                                                EventRequestName),
                                 var_declarator(trigger,EventRequestName))),
                         EventRequestComment),
        % generate bthread watching triggers
        atom_concat(Spec, 'BThread watching trigger vars',
                    WatchingComment),
        comment_sorround(forall(state(Spec,T,S),
                                (spec_bthread_state_member_name(Spec,T,S,
                                                                watch_trigger,
                                                                WatchVar),
                                 var_declarator(trigger,WatchVar))),
                         WatchingComment),
        % generate bthread requesting triggers
        atom_concat(Spec, 'BThread requesting trigger vars',
                    RequestingComment),
        comment_sorround(forall(state(Spec,T,S),
                                (spec_bthread_state_member_name(Spec,T,S,
                                                                request_trigger,
                                                                RequestVar),
                                 var_declarator(trigger,RequestVar))),
                         RequestingComment),
        % generate bthread blocking triggers
        atom_concat(Spec, 'BThread blocking trigger vars',
                    BlockingComment),
        comment_sorround(forall(state(Spec,T,S),
                                (spec_bthread_state_member_name(Spec,T,S,
                                                                block_trigger,
                                                                BlockVar),
                                 var_declarator(trigger,BlockVar))),
                         BlockingComment),
	% generate state code
	forall(state(Spec, T, S),
	       gen_state(Spec, T, S)),
        % generate state init code
        atom_concat(Spec,' State init code',StateComment),
        comment_sorround(gen_state_init_code(Spec),
                         StateComment),
	% generate bthread init code
	atom_concat(Spec,' BTHREADS INIT',BThreadsComment),
	sort_threads_by_priority(Spec,ThreadsByPriority),
	comment_sorround(gen_bthreads_init_code(Spec,ThreadsByPriority),
			 BThreadsComment),
	gen_event_to_fire(Spec),
        gen_bthread_runner(Spec),
	gen_superstepper(Spec),
	gen_fire_external_event(Spec),
	told,
        gen_state_code_func_file(Spec),
	cleanup_temps,
        go_back_to_base.

gen_state_code_func_file(Spec):-
        atom_concat(Spec,'_code.galaxy',OutputCodeFileName),
        syso('writing code funcs file'),
        tell(OutputCodeFileName),
        open(OutputCodeFileName, write,_OutputCodeStream,[]),
        spec_member_name(Spec,set_transit,SetTransitFunc),
        comment('functions that implement the state business logic'),
        forall(state(Spec,T,S),
               (spec_bthread_state_member_name(Spec,T,S,code_func,CodeFuncName),
                func_declarator(bool,
                                CodeFuncName,
                                [],
                                (func_call(SetTransitFunc,[true]),
                                 ret_true)))),
        told.        

gen_bthreads_init_code(Spec,ThreadsByPriority):-
        syso('generating bthreads init code'),
        func_declarator(void,bthreads_init,[],
                        gen_bthreads_init_code_helper(Spec,
                                                      ThreadsByPriority,0)),!.
gen_bthreads_init_code_helper(_Spec,[],_CurrInd):-!.
gen_bthreads_init_code_helper(Spec,[T|Rest],CurrInd):-
        syso('bthreads_init_code_helper current thread <~w>',[T]),
	bthread(Spec,T,P),
	comment(T),
	start_state(Spec,T,Start),
	bthread_id(T,ID),
	bthread_state_id(T, Start, _SID),
	% priority
        syso('setting bthread priority'),
	spec_member_name(Spec,bthreads_priorities,BThreadsPriorities),
	spec_member_name(Spec,bthread_ids_by_priority,BThreadsIds),
	array_var_setter(BThreadsPriorities,CurrInd,P,BThreadPrioritySetter),
	array_var_setter(BThreadsIds,CurrInd,ID,BThreadIdSetter),
	format_code(BThreadPrioritySetter),
	format_code(BThreadIdSetter),
	% current state
        syso('setting current state'),
	spec_member_name(Spec,current_bthread_state_code,BThreadsCurrentState),
	spec_bthread_state_member_name(Spec,T,Start,code,CurrentStateTrigger),
	array_var_setter(BThreadsCurrentState,ID,
                         CurrentStateTrigger,CurrentStateSetter),
	format_code(CurrentStateSetter),
	% blocking trigger
        syso('setting blocking trigger'),
	spec_member_name(Spec,current_bthread_blocking_triggers,
                         BThreadsBlockingTriggers),
	spec_bthread_state_member_name(Spec,T,Start,block_trigger,BlockTrigger),
	array_var_setter(BThreadsBlockingTriggers,ID,BlockTrigger,
                         BlockTriggerSetter),
	format_code(BlockTriggerSetter),
	% recursive call
	NextInd is CurrInd + 1,
	gen_bthreads_init_code_helper(Spec,Rest,NextInd),!.

gen_state_init_code(Spec):-
        syso('generating state init code'),
        Body = forall(state(Spec,T,S),
                      (% event-request
                       spec_bthread_state_member_name(Spec,T,S,
                                                      event_request,
                                                      EventRequestVarName),
                       spec_bthread_state_member_name(Spec,T,S,
                                                      event_request_func,
                                                      EventRequestFunc),
                       % request
                       spec_bthread_state_member_name(Spec,T,S,request,
                                                      RequestFunc),
                       spec_bthread_state_member_name(Spec,T,S,
                                                      request_trigger,
                                                      RequestTrigger),
                       % watch
                       spec_bthread_state_member_name(Spec,T,S,watch,
                                                      WatchFunc),
                       spec_bthread_state_member_name(Spec,T,S,
                                                      watch_trigger,WatchTrigger),
                       % block
                       spec_bthread_state_member_name(Spec,T,S,block,
                                                      BlockFunc),
                       spec_bthread_state_member_name(Spec,T,S,
                                                      block_trigger,BlockTrigger),
                       % code trigger
                       spec_bthread_state_member_name(Spec,T,S,
                                                      code,
                                                      TriggerVarName),
                       spec_bthread_state_member_name(Spec,T,S,
                                                      code_func,
                                                      TriggerFuncName),
                       trigger_var_setter(EventRequestVarName,EventRequestFunc),
                       trigger_var_setter(RequestTrigger,RequestFunc),
                       trigger_var_setter(WatchTrigger,WatchFunc),
                       trigger_var_setter(BlockTrigger,BlockFunc),
                       trigger_var_setter(TriggerVarName,
                                          TriggerFuncName))),
        func_declarator(void,
                        state_init,
                        [],
                        Body).
                       
                

gen_conventions:-
	format('// START coding shorthand conventions'),nl,
	% 2nd parameter to TriggerExecute
	var_declarator(bool,force,false),
	var_declarator(bool,test,true),
	% 3rd parameter to TriggerExecute
	var_declarator(bool, join, true),
	var_declarator(bool, asynch, false),
        var_declarator(int,noMoreEventsToRequest,-1),
	format('// END coding shorthand conventions'),nl,nl.

gen_spec_globals(Spec):-
        atom_concat(Spec,' globals',SpecGlobalsComment),
	comment_sorround((gen_var(Spec,triggered_event,int),
                          syso('generated triggered event'),
                          gen_var(Spec,requested,bool),
                          gen_var(Spec,watched,bool),
                          gen_var(Spec,blocked,bool),
                          syso('generated blocked'),
                          gen_var(Spec,transit,bool),
                          syso('generated transit'),
                          var_setter_getter(Spec,transit,bool),
                          gen_bthread_lists(Spec),
                          syso('generated bthread lists')),
                         SpecGlobalsComment).

gen_bthread_lists(Spec):-
	all_spec_bthreads(Spec,Threads),
	length(Threads,NumOfThreads),
	gen_list(Spec,bthreads_priorities,int,NumOfThreads),
	gen_list(Spec,bthread_ids_by_priority,int,NumOfThreads),
	gen_list(Spec,current_bthread_state_code,trigger,NumOfThreads),
	gen_list(Spec,
		 current_bthread_event_requesting_triggers,
		 trigger,NumOfThreads),
        gen_list(Spec,current_bthread_watching_triggers,trigger,NumOfThreads),
        gen_list(Spec,current_bthread_requesting_triggers,trigger,NumOfThreads),
	gen_list(Spec,current_bthread_blocking_triggers,trigger,NumOfThreads),
        gen_list(Spec,current_bthread_transit_triggers,trigger,NumOfThreads).
	
gen_fire_external_event(Spec):-
        syso('generating fire external event'),
	spec_member_name(Spec,fire_external_event,FuncName),
	spec_member_name(Spec,superstepper,Superstepper),
	spec_member_name(Spec,triggered_event,TriggeredEvent),
	var_setter(TriggeredEvent,e,TriggeredEventSetter),
	func_call(Superstepper,[],SuperCall),
        FuncBody = (format_code(TriggeredEventSetter),
                    format_code(SuperCall),semicolon_nl),
	func_declarator(void,
			FuncName,
			[[int,e]],
			FuncBody),
	nl.

gen_bevent(Spec,E):-
	spec_bevent_var_name(Spec,E,BEvent),
	% syso('generating bevent <~w>',[E]),
	bevent_id_idgen(ID),
	% syso('location of bevent <~w> in contingency vector is ~w', [E, ID]),
	var_declarator(int,BEvent,ID),
	assert(bevent_id(E, ID)).

or_chain_cond(_Spec,[],'false'):-!.
or_chain_cond(Spec,Events,Line):-
	triggered_event_var_name(Spec,Var),
	or_chain_cond_helper(Spec,Var,Events,Line).
or_chain_cond_helper(_Spec,TriggeredEventVar,[E],Line):-
	eqeq_cond(TriggeredEventVar,E,Line),!.
or_chain_cond_helper(Spec,TriggeredEventVar,[E|Rest],Line):-
	!,
	or_chain_cond_helper(Spec,TriggeredEventVar,Rest,RestOfLine),
	eqeq_cond(TriggeredEventVar,E,Part),
	or_cond(Part,RestOfLine,Line).
	
gen_rwb_trigger_by_name(Spec,T,S,Func):-
	syso('generating bthread <~w> ~w trigger function for state ~w',
             [T,Func,S]),
        atom_concat(Func,'ed',Funced),
        spec_member_name(Spec,Funced,FuncRetVar),
	spec_bthread_state_member_name(Spec,T,S,Func,FuncName),
	Pred =.. [Func,Spec,T,S,E],
	findall(E,
		Pred,
		Events),
	(Events = [] ->
	 Body = var_setter(FuncRetVar,false);
	 Body = (findall(QualifiedE,
			 (member(E,Events),
			  spec_bevent_var_name(Spec,E,QualifiedE)),
			 QualifiedEvents),
		 or_chain_cond(Spec,QualifiedEvents,Conds),
		 if_statement(Conds,
                              var_setter(FuncRetVar,true),
                              var_setter(FuncRetVar,false)))),
	trigger_declarator(FuncName,Body).

gen_state(Spec,T,S):-
	syso('generating state <~w:~w>',[T,S]),
	bthread_state_id(T, S, ID),
	spec_bthread_state_member_name(Spec,T,S,'',StateComment),
	spec_bthread_state_member_name(Spec,T,S,'id',StateVar),
        comment_sorround((var_declarator(int,StateVar,ID),
                          gen_rwb_trigger_by_name(Spec, T, S,watch),
                          gen_rwb_trigger_by_name(Spec, T, S,request),
                          gen_rwb_trigger_by_name(Spec, T, S,block),
                          gen_state_transition_func(Spec,T,S),
                          gen_state_event_request_trigger(Spec,T,S)),
                         StateComment).

gen_state_event_request_trigger(Spec,T,S):-
        syso('generating event requesting trigger for state <~w>',[S]),
	findall(E,
		request(Spec,T,S,E),
		EventsToFire),
	length(EventsToFire,Len),
	spec_bthread_state_member_name(Spec,T,S,event_request_func,FuncName),
	(Len is 0 ->
	 Body = call(ret_true);
	 spec_member_name(Spec,triggered_event,TriggeredEvent),
	 spec_bthread_state_member_name(Spec,T,S,event_requested_list_ind,Ind),
	 spec_bthread_state_member_name(Spec,T,S,event_requested_list,List),
	 var_declarator(int,Ind,0),
	 array_var_declarator(List,int,Len),
	 array_var_accessor(List,Ind,Accessor),
	 gte_cond(Ind,Len,Cond),
	 Body = (if_statement(Cond,
			      (var_setter(TriggeredEvent,Accessor),
			       nextInt(Ind),
			       ret_true)),
		 var_setter(Ind,0),
		 var_setter(TriggeredEvent,'-1'))),
        trigger_declarator(FuncName,Body).

gen_state_transition_func(Spec,T,S):-
        syso('generating transition func bthread <~w,~w>',[T,S]),
	spec_bthread_state_member_name(Spec,T,S,transit,FuncName),
	trigger_declarator(FuncName,
                           forall(transition(Spec, T, S, S2, E),
                                  gen_transition_clause(Spec,T,S2,E))).

gen_transition_clause(Spec,T,S2,E):-
	syso('generating transition <~w --> ~w>', [E, S2]),
	bthread_id(T,TIndex),
	spec_member_name(Spec,current_bthread_state_code,StateTriggers),
	spec_bthread_state_member_name(Spec,T,S2,code,NextStateTrigger),
	spec_member_name(Spec,
			 current_bthread_event_requesting_triggers,
			 EventRequestingTriggers),
	spec_bthread_state_member_name(Spec,T,S2,event_request,
                                       NextEventRequestTrigger),
	spec_member_name(Spec,
			 current_bthread_watching_triggers,
			 WatchingTriggers),
	spec_member_name(Spec,
			 current_bthread_requesting_triggers,
			 RequestingTriggers),
	spec_member_name(Spec,
			 current_bthread_blocking_triggers,
			 BlockingTriggers),
	spec_bthread_state_member_name(Spec,T,S2,watch_trigger,
                                       NextWatchingTrigger),
	spec_bthread_state_member_name(Spec,T,S2,request_trigger,
                                       NextRequestingTrigger),
	spec_bthread_state_member_name(Spec,T,S2,block_trigger,
                                       NextBlockingTrigger),
	spec_member_name(Spec,triggered_event,TriggeredEvent),
	spec_bevent_var_name(Spec,E,EVarName),
	eqeq_cond(TriggeredEvent,EVarName,Cond),
	% next state trigger
	array_var_setter(StateTriggers,
			 TIndex,
			 NextStateTrigger,
			 NextStateTriggerSetter),
	% next event requesting trigger
	array_var_setter(EventRequestingTriggers,
			 TIndex,
			 NextEventRequestTrigger,
			 NextEventRequestTriggerSetter),
	% next blocking trigger
	array_var_setter(WatchingTriggers,
			 TIndex,
			 NextWatchingTrigger,
			 NextWatchingTriggerSetter),
	% next blocking trigger
	array_var_setter(RequestingTriggers,
			 TIndex,
			 NextRequestingTrigger,
			 NextRequestingTriggerSetter),
	% next blocking trigger
	array_var_setter(BlockingTriggers,
			 TIndex,
			 NextBlockingTrigger,
			 NextBlockingTriggerSetter),
	% the triggered event chosen in bid
	if_statement(Cond,
		     (format_code(NextStateTriggerSetter),
		      format_code(NextEventRequestTriggerSetter),
		      format_code(NextWatchingTriggerSetter),
                      format_code(NextRequestingTriggerSetter),
                      format_code(NextBlockingTriggerSetter))).

gen_event_to_fire(Spec):-
	syso('generating event_to_fire_chooser'),
	spec_member_name(Spec,blocked,IsBlocked),
	spec_member_name(Spec,triggered_event,TriggeredEvent),
	spec_member_name(Spec,event_to_fire,EventToFireFuncName),
	spec_member_name(Spec,is_blocked,IsBlockedFuncName),
	spec_member_name(Spec,current_bthread_event_requesting_triggers,
                         EventFiringTriggers),
	spec_member_name(Spec,bthread_ids_by_priority,BThreadsByPriority),
	spec_member_name(Spec,current_bthread_blocking_triggers,BlockingTriggers),
	array_var_accessor(EventFiringTriggers,currentBThreadId,
                           EventFiringAccessor),
	func_call(IsBlockedFuncName,[],IsBlockedCall),
	neg(IsBlockedCall,Unblocked),
	neq_cond(TriggeredEvent,'-1',NoTriggeredCond),
        IsBlockedFuncBody =
        (var_declarator(trigger,currentBlocker),
         loop_over_array(BlockingTriggers,
                         (array_var_accessor(BlockingTriggers,IsBlockedInd,
                                             BlockingTriggersAccessor),
                          var_setter(currentBlocker,
                                     BlockingTriggersAccessor),
                          closure_call_synch(currentBlocker,[]),
                          if_statement(IsBlocked,call(ret_true))),
                         IsBlockedInd),
         ret_false),
        EventToFireFuncBody =
        (var_declarator(trigger,currentBThreadRequester),
         var_declarator(int,currentBThreadId),
         loop_over_array(BThreadsByPriority,
                         (array_var_accessor(BThreadsByPriority,EventToFireInd,
                                             BThreadsAccessor),
                          var_setter(currentBThreadId,BThreadsAccessor),
                          var_setter(currentBThreadRequester,
                                     EventFiringAccessor),
                          closure_call_synch(currentBThreadRequester,[]),
                          while_loop(NoTriggeredCond,
                                     if_statement(Unblocked,
                                                  call(ret)),
                                     closure_call_synch(currentBThreadRequester,
                                                        [])),
                          ret),
                         EventToFireInd)),
	func_declarator(bool,
			IsBlockedFuncName,
			[],
			IsBlockedFuncBody),
        syso('generated isBlockedFuncBody'),
	func_declarator(void,
			EventToFireFuncName,
			[],
			EventToFireFuncBody),
        syso('generated EventToFireFuncBody'),
	nl.

gen_bthread_runner(Spec):-
        syso('generating bthread runner function'),
	% spec_member_name(Spec,bthread_done,Done),
        spec_member_name(Spec,transit,Transit),
        spec_member_name(Spec,current_bthread_state_code,StateCodeList),
        array_var_accessor(StateCodeList,bthreadIndex,CodeTrigger),
        spec_member_name(Spec,current_bthread_transit_triggers,TransitTrigList),
        array_var_accessor(TransitTrigList,bthreadIndex,TransitTrigger),
        spec_member_name(Spec,run_bthread,Run),
        func_declarator(bool,
                        Run,
                        [[int,bthreadIndex]],
                        (closure_call_synch(CodeTrigger,[]),
                         if_statement(Transit,
                                      (closure_call_synch(TransitTrigger,
                                                          []),
                                       ret_true)),
                         ret_false)).

gen_superstepper(Spec):-
	syso('generating superstepper'),
        spec_member_name(Spec,run_bthread,RunBthread),
        spec_member_name(Spec,requested,RequestedVar),
        spec_member_name(Spec,watched,WatchedVar),
        or_cond(WatchedVar,RequestedVar,ResumeCond),
	spec_member_name(Spec,superstepper,Name),
	spec_member_name(Spec,triggered_event,TriggeredEvent),
	spec_member_name(Spec,event_to_fire,EventToFire),
	spec_member_name(Spec,current_bthread_state_code,CurrentStateTriggers),
	spec_member_name(Spec,current_bthread_requesting_triggers,
                         RequestingTriggers),
	spec_member_name(Spec,current_bthread_watching_triggers,
                         WatchingTriggers),
	neq_cond(TriggeredEvent,noMoreEventsToRequest,Cond),
	func_call(EventToFire,[],EventToFireCall),
	atom_concat(EventToFireCall,';\n',Step),
        loop_over_array(CurrentStateTriggers,
                        (array_var_accessor(RequestingTriggers,
                                            CodeTriggerInd,
                                            RequestTrigger),
                         array_var_accessor(WatchingTriggers,
                                            CodeTriggerInd,
                                            WatchTrigger),
                         closure_call_synch(RequestTrigger,[]),
                         closure_call_synch(WatchTrigger,[]),
                         if_statement(ResumeCond,
                                      func_call(RunBthread,[CodeTriggerInd]))),
                        CodeTriggerInd,
                        CodeTriggerIndDec,
                        Loop),
        SuperStepperBody = (CodeTriggerIndDec, while_loop(Cond,Loop,Step)),
	func_declarator(void,
			Name,
			[],
			SuperStepperBody),
	nl.

cleanup_temps:-
	retractall(bevent_id(_,_)),
	retractall(bthread_id(_,_)),
	retractall(bthread_state_id(_)),
	bthread_id_clean,
	bevent_id_clean,
	bthread_state_id_clean,
	loop_index_clean,
	retractall(closure_arg(_,_)).

:- syso('Galaxy BP generator loaded.').